package com.sunnada.poi.excel.base;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.util.StringUtils;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @program: poiextended
 * @description: 导出Excel抽象
 * @author: 李瑞益
 * @create: 2018-11-06 15:56
 **/
public abstract class ExportBase {
    /**Excel版本为03版*/
    public static final int EXECEL_VERSION_03 = 0;

    /**Excel版本为07版*/
    public static final int EXECEL_VERSION_07 = 1;

    /**标题所占行数*/
//    private int mergeRowCount;

    /**
     * 获取指定版本
     * @param workbookVersion
     * @return
     */
    public Workbook getWorkbook(int workbookVersion){
        Workbook wb = null;
        if(workbookVersion == 0){
            wb = new HSSFWorkbook();
        }else{
            wb = new XSSFWorkbook();
        }
        return wb;
    }

    /**
     * 以默认样式导出Excel到本地
     * @param excelVersion 创建Excel的工具版本
     * @param titleName 标题名
     * @param list 数据集
     * @param headerNameMap 列头名
     * @param filePath 导出的路径
     */
    public <T> void exportToLocalExcel(int excelVersion, String titleName, List<T> list, Map<String, String> headerNameMap,
                                       String filePath) {
        Workbook wb = getWorkbook(excelVersion);
        builderExcelData(wb,list,titleName,headerNameMap);
        write2Excel(wb,filePath);
        System.out.println("写出完成");
    }


    /**
     * 以默认样式在客户端导出（即客户端下载）
     */
    public <T> void exportToClientExcel(int excelVersion, String titleName, List<T> list, Map<String, String> headerNameMap,
                                        HttpServletResponse response, String fileName) {
        Workbook wb = getWorkbook(excelVersion);
        builderExcelData(wb,list,titleName,headerNameMap);
        write2Excel(wb,response,fileName);
        System.out.println("写出完成");
    }


    /**
     * 多个数据分页
     * @param excelVersion
     * @param titleName
     * @param lists
     * @param headerNameMaps
     * @param response
     * @param fileName
     * @param <T>
     */
    public <T> void exportMultipleToClientExcel(int excelVersion, String titleName, List<List<T>> lists,
                                                Map<Integer,Map<String, String>> headerNameMaps,
                                                HttpServletResponse response,String fileName) {
        //多个数据分页
        Workbook wb = getWorkbook(excelVersion);
        for(Map.Entry<Integer,Map<String,String>> entry : headerNameMaps.entrySet()){
            builderExcelData(wb,lists.get(entry.getKey()),titleName,entry.getValue());
        }
        write2Excel(wb,response,fileName);
        System.out.println("写出完成");
    }
    /**
     * 多个数据分页
     * @param excelVersion
     * @param titleName
     * @param lists
     * @param headerNameMaps
     * @param filePath
     * @param <T>
     */
    public <T> void exportMultipleToLocalExcel(int excelVersion, String titleName, List<List<T>> lists,
                                               List<Map<String, String>> headerNameMaps,
                                               String filePath) {
        //多个数据分页
        Workbook wb = getWorkbook(excelVersion);
        for(int i = 0 ; i < headerNameMaps.size() ; i ++){
            builderExcelData(wb,lists.get(i),titleName,headerNameMaps.get(i));
        }
        write2Excel(wb,filePath);
        System.out.println("写出完成");
    }

    /**
     * 一个数据源的Excel数据构建
     * @param wb
     * @param list
     * @param titleName
     * @param headerNameMap
     * @param <T>
     */
    public <T> void builderExcelData(Workbook wb,List<T> list,String titleName,Map<String, String>  headerNameMap){
        //这里可能有超过一页65535的数据
        if(list.size() > 65535){
            //每页要一个新的sheet和一个style
            Sheet newSheet = null;
            StyleBase newStyle = null;
            //获取每分的数据（即每页多少的数据）
            List<List<T>> lists = partList(list, 65535);
            for(List<T> currentList : lists){
                newSheet = wb.createSheet();
                newStyle = new StyleBase();
                //设置标题
                setTitle(newSheet,titleName,newStyle,newStyle.defaultHeaderStyle(wb));
                //列头
                setColumnHeader(newSheet,headerNameMap,newStyle,newStyle.defaultHeaderStyle(wb));
                //内容
                setContentData(newSheet,currentList,headerNameMap,newStyle,newStyle.defaultContentStyle(wb));
            }
        }
        else{
            Sheet sheet = wb.createSheet();
            StyleBase style = new StyleBase();
            if(!StringUtils.isEmpty(titleName)){
                //设置标题
                setTitle(sheet,titleName,style,style.defaultHeaderStyle(wb));
            }
            setColumnHeader(sheet,headerNameMap,style,style.defaultHeaderStyle(wb));
            setContentData(sheet,list,headerNameMap,style,style.defaultContentStyle(wb));
        }
    }

    /**
     * 设置标题
     * @param sheet 工作簿
     * @param titleName 标题名
     * @param style 样式类对象
     * @param cellStyle 标题单元格样式
     * @return
     */
    public Cell setTitle(Sheet sheet, String titleName, StyleBase style, CellStyle cellStyle) {
        Cell cell = style.setTitleCell(sheet);
        cell.setCellValue(titleName);
        cell.setCellStyle(cellStyle);
        return cell;
    }

    /**
     * 设置列头
     * @param headerNameMap
     * @param style
     * @param cellStyle
     */
    public void setColumnHeader(Sheet sheet, Map<String,String> headerNameMap, StyleBase style, CellStyle cellStyle) {
        int hi = 0;//表头开始索引（横向）
        Row headerRow = sheet.createRow(style.getMergeRowCount());
        for (Map.Entry<String, String> entry : headerNameMap.entrySet()) {
            headerRow.createCell(hi).setCellValue(entry.getValue());
            //设置
            headerRow.getCell(hi).setCellStyle(cellStyle);
            hi++;
        }
        style.setMergeRowCount(style.getMergeRowCount()+1);
    }


    /**
     * 设置数据内容
     * @param sheet
     * @param list
     * @param headerNameMap
     * @param style
     * @param cellStyle
     * @param <T>
     */
    public <T> void setContentData(Sheet sheet, List<T> list, Map<String, String> headerNameMap,
                                   StyleBase style, CellStyle cellStyle) {
        int rowIndex = style.getMergeRowCount();
        builderData(rowIndex,sheet,headerNameMap,list,cellStyle);
    }

    public abstract <T> void builderData(int rowIndex,Sheet sheet,Map<String,String> headerNameMap,List<T> list,CellStyle cellStyle);

    /**
     * 设置单元格的值
     * @param cell 单元格对象
     * @param obj 实体类
     * @param fieldName 对应实体类属性的表头名（字段名或属性名）
     */
    public void setCellValue(Cell cell, Object obj, String fieldName) {
        //当title为null时
        //不会null时
        String methodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        Class aClass = obj.getClass();
        Method[] methods = aClass.getClass().getDeclaredMethods();//获取方法集合
        try {
            Method getMethod = aClass.getMethod(methodName, new Class[]{});//通过名字获取到方法
            Object invokeValue = getMethod.invoke(obj, new Object[]{});
            String value;
            if (invokeValue != null) {
                value = String.valueOf(invokeValue);
            } else {
                value = null;
            }
            cell.setCellValue(value);
            System.out.print(value + "\t");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    /**
     * 写出到Excel
     * @param filePath 写出的目标路径
     * @param wb
     */
    public void write2Excel(Workbook wb,String filePath) {
        //写出到xls
        File file = new File(filePath);
        try {
            if (!file.exists()) {
                file.createNewFile();
            }
            OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
            System.out.println(file.getAbsoluteFile());
            wb.write(os);
            os.close();
            wb.close();
            System.out.println("数据已写出到："+file.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 客户端下载
     * @param wb
     * @param response
     * @param fileName
     */
    public void write2Excel(Workbook wb,HttpServletResponse response,String fileName){
        String suffix = ".xlsx";
        if(wb instanceof HSSFWorkbook){
            suffix = ".xls";
        }
        response.setHeader("Content-Disposition", "attachment;filename=" +fileName + suffix);
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        try {
            ServletOutputStream outputStream = response.getOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(outputStream);
            wb.write(bos);
            bos.flush();
            bos.close();
            wb.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 按指定的大小分割list
     * @param list 原始的list集合
     * @param len 要切割的list的长度
     * @param <T>
     * @return
     */
    public <T> List<List<T>> partList(List<T> list, int len){
        if (list != null && list.size() > len) {
            List<List<T>> lists = new ArrayList<>();
            double part = Math.ceil(list.size() / (double)len);
            for (int i = 0; i < part; i++) {
                if (i != part - 1) {
                    lists.add(list.subList(i*len,len*(i+1)-1));
                }else{
                    lists.add(list.subList(i*len,list.size()-1));
                }
            }
            return lists;
        }
        return null;
    }
}
